package org.jacoco.core.runtime;

import org.jacoco.core.data.*;
import org.jacoco.core.internal.instr.InstrSupport;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class RuntimeData {

    /**
     * store for execution data
     */
    protected final ExecutionDataStore store;

    private long startTimeStamp;

    private final String sessionId;

    public RuntimeData(String sessionId) {
        this.store = new ExecutionDataStore();
        this.sessionId = sessionId;
        this.startTimeStamp = System.currentTimeMillis();
    }

    public String getSessionId() {
        return sessionId;
    }

    public final void collect(final IExecutionDataVisitor executionDataVisitor,
                              final ISessionInfoVisitor sessionInfoVisitor, final boolean reset) {
        synchronized (store) {
            final SessionInfo info = new SessionInfo(sessionId, startTimeStamp,
                    System.currentTimeMillis());
            sessionInfoVisitor.visitSessionInfo(info);
            store.accept(executionDataVisitor);
            if (reset) {
                reset();
            }
        }
    }

    public final void reset() {
        synchronized (store) {
            store.reset();
            startTimeStamp = System.currentTimeMillis();
        }
    }

    public ExecutionData getExecutionData(final Long id, final String name,
                                          final int probecount) {
        synchronized (store) {
            return store.get(id, name, probecount);
        }
    }

    /**
     * Retrieves the execution probe array for a given class. The passed
     * {@link Object} array instance is used for parameters and the return value
     * as follows. Call parameters:
     *
     * <ul>
     * <li>args[0]: class id ({@link Long})
     * <li>args[1]: vm class name ({@link String})
     * <li>args[2]: probe count ({@link Integer})
     * </ul>
     *
     * Return value:
     *
     * <ul>
     * <li>args[0]: probe array (<code>boolean[]</code>)
     * </ul>
     *
     * @param args parameter array of length 3
     */
    public void getProbes(final Object[] args) {
        final Long classid = (Long) args[0];
        final String name = (String) args[1];
        final int probecount = ((Integer) args[2]).intValue();
        args[0] = getExecutionData(classid, name, probecount).getProbes();
    }

    /**
     * In violation of the regular semantic of {@link Object#equals(Object)}
     * this implementation is used as the interface to the execution data store.
     *
     * @param args the arguments as an {@link Object} array
     *
     * @return has no meaning
     */
    @Override
    public boolean equals(final Object args) {
        if (args instanceof Object[]) {
            getProbes((Object[]) args);
        }
        return super.equals(args);
    }

    /**
     * Generates code that creates the argument array for the
     * {@link #getProbes(Object[])} method. The array instance is left on the
     * operand stack. The generated code requires a stack size of 5.
     *
     * @param classid    class identifier
     * @param classname  VM class name
     * @param probecount probe count for this class
     * @param mv         visitor to emit generated code
     */
    public static void generateArgumentArray(final long classid,
                                             final String classname, final int probecount,
                                             final MethodVisitor mv) {
        mv.visitInsn(Opcodes.ICONST_3);
        mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");

        // Class Id:
        mv.visitInsn(Opcodes.DUP);
        mv.visitInsn(Opcodes.ICONST_0);
        mv.visitLdcInsn(Long.valueOf(classid));
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",
                "(J)Ljava/lang/Long;", false);
        mv.visitInsn(Opcodes.AASTORE);

        // Class Name:
        mv.visitInsn(Opcodes.DUP);
        mv.visitInsn(Opcodes.ICONST_1);
        mv.visitLdcInsn(classname);
        mv.visitInsn(Opcodes.AASTORE);

        // Probe Count:
        mv.visitInsn(Opcodes.DUP);
        mv.visitInsn(Opcodes.ICONST_2);
        InstrSupport.push(mv, probecount);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Integer", "valueOf",
                "(I)Ljava/lang/Integer;", false);
        mv.visitInsn(Opcodes.AASTORE);
    }

    /**
     * Generates the code that calls a {@link RuntimeData} instance through the
     * JRE API method {@link Object#equals(Object)}. The code pops a
     * {@link Object} instance from the stack and pushes the probe array of type
     * <code>boolean[]</code> on the operand stack. The generated code requires
     * a stack size of 6.
     *
     * @param classid    class identifier
     * @param classname  VM class name
     * @param probecount probe count for this class
     * @param mv         visitor to emit generated code
     */
    public static void generateAccessCall(final long classid,
                                          final String classname, final int probecount,
                                          final MethodVisitor mv) {
        // stack[0]: Ljava/lang/Object;

        generateArgumentArray(classid, classname, probecount, mv);

        // stack[1]: [Ljava/lang/Object;
        // stack[0]: Ljava/lang/Object;

        mv.visitInsn(Opcodes.DUP_X1);

        // stack[2]: [Ljava/lang/Object;
        // stack[1]: Ljava/lang/Object;
        // stack[0]: [Ljava/lang/Object;

        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "equals",
                "(Ljava/lang/Object;)Z", false);
        mv.visitInsn(Opcodes.POP);

        // stack[0]: [Ljava/lang/Object;

        mv.visitInsn(Opcodes.ICONST_0);
        mv.visitInsn(Opcodes.AALOAD);

        // stack[0]: [Z

        mv.visitTypeInsn(Opcodes.CHECKCAST, InstrSupport.DATAFIELD_DESC);
    }

}
